package org.github.manx98.jlibhac.tools.fssystem;

import org.github.manx98.jlibhac.fs.IStorage;

import java.io.EOFException;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @author Manx98
 * @version 1.0
 * @since 2022/10/4 0:49
 */
public class CachedStorage extends IStorage {
    private final IStorage baseStorage;
    private final int blockSize;
    private final boolean leaveOpen;
    private final long length;
    private final CacheBlock block;

    public CachedStorage(SectorStorage baseStorage, int cacheSize, boolean leaveOpen) {
        this(baseStorage, baseStorage.getSectorSize(), cacheSize, leaveOpen);
    }

    public CachedStorage(IStorage baseStorage, int blockSize, int cacheSize, boolean leaveOpen) {
        super(0);
        this.baseStorage = baseStorage;
        this.blockSize = blockSize;
        this.leaveOpen = leaveOpen;

        long baseSize = baseStorage.getSize();
        this.length = baseSize;
        this.block = new CacheBlock();
        block.Buffer = new byte[blockSize];
        block.Index = -1;
    }

    @Override
    public void read(long offset, byte[] destination, int destOffset, int len) throws IOException {
        long remaining = len;
        long inOffset = offset;
        int outOffset = 0;

        if (!checkAccessRange(offset, len, length)) {
            throw new EOFException();
        }
        while (remaining > 0) {
            long blockIndex = inOffset / blockSize;
            int blockPos = (int) (inOffset % blockSize);
            CacheBlock block = getBlock(blockIndex);

            int bytesToRead = (int) Math.min(remaining, blockSize - blockPos);
            System.arraycopy(block.Buffer, blockPos, destination, destOffset + outOffset, bytesToRead);

            outOffset += bytesToRead;
            inOffset += bytesToRead;
            remaining -= bytesToRead;
        }
    }

    private CacheBlock getBlock(long blockIndex) throws IOException {
        readBlock(block, blockIndex);
        return block;
    }

    private void readBlock(CacheBlock block, long index) throws IOException {
        long offset = index * blockSize;
        int length = blockSize;
        if (this.length != -1) {
            length = (int) Math.min(this.length - offset, length);
        }
        baseStorage.read(offset, block.Buffer, 0, length);
        block.Length = length;
        block.Index = index;
        block.Dirty = false;
    }

    public static boolean checkAccessRange(long offset, long size, long totalSize) {
        return offset >= 0 &&
                size >= 0 &&
                size <= totalSize &&
                offset <= totalSize - size;
    }

    @Override
    public long getSize() {
        return length;
    }

    private class CacheBlock {
        public long Index;
        public byte[] Buffer;
        public int Length;
        public boolean Dirty;
    }
}
